﻿using System;
using System.Collections;
using System.Collections.Generic;
using Sofire.Expressions.Domain;
using Antlr.Runtime;
using System.Diagnostics;
using System.Threading;

namespace Sofire.Expressions
{
    public class Expression
    {
        public EvaluateOptions Options { get; set; }

        /// <summary>
        /// Textual representation of the expression to evaluate.
        /// </summary>
        protected string OriginalExpression;

        public Expression(string expression)
            : this(expression, EvaluateOptions.None)
        {
        }

        public Expression(string expression, EvaluateOptions options)
        {
            if(String.IsNullOrEmpty(expression))
                throw new
                    ArgumentException("空表达式。", "expression");

            OriginalExpression = expression;
            Options = options;
        }

        public Expression(LogicalExpression expression)
            : this(expression, EvaluateOptions.None)
        {
        }

        public Expression(LogicalExpression expression, EvaluateOptions options)
        {
            if(expression == null)
                throw new
                    ArgumentException("表达式是一个 null 值。", "expression");

            ParsedExpression = expression;
            Options = options;
        }

        #region Cache management
        private static bool _cacheEnabled = true;
        private static Dictionary<string, WeakReference> _compiledExpressions = new Dictionary<string, WeakReference>();
        private static readonly ReaderWriterLock Rwl = new ReaderWriterLock();

        public static bool CacheEnabled
        {
            get { return _cacheEnabled; }
            set
            {
                _cacheEnabled = value;

                if(!CacheEnabled)
                {
                    // Clears cache
                    _compiledExpressions = new Dictionary<string, WeakReference>();
                }
            }
        }

        /// <summary>
        /// Removed unused entries from cached compiled expression
        /// </summary>
        private static void CleanCache()
        {
            var keysToRemove = new List<string>();

            try
            {
                Rwl.AcquireWriterLock(Timeout.Infinite);
                foreach(var de in _compiledExpressions)
                {
                    if(!de.Value.IsAlive)
                    {
                        keysToRemove.Add(de.Key);
                    }
                }


                foreach(string key in keysToRemove)
                {
                    _compiledExpressions.Remove(key);
                    Trace.TraceInformation("Cache entry released: " + key);
                }
            }
            finally
            {
                Rwl.ReleaseReaderLock();
            }
        }

        #endregion

        public static LogicalExpression Compile(string expression, bool nocache)
        {
            LogicalExpression logicalExpression = null;

            if(_cacheEnabled && !nocache)
            {
                try
                {
                    Rwl.AcquireReaderLock(Timeout.Infinite);

                    if(_compiledExpressions.ContainsKey(expression))
                    {
                        Trace.TraceInformation("Expression retrieved from cache: " + expression);
                        var wr = _compiledExpressions[expression];
                        logicalExpression = wr.Target as LogicalExpression;

                        if(wr.IsAlive && logicalExpression != null)
                        {
                            return logicalExpression;
                        }
                    }
                }
                finally
                {
                    Rwl.ReleaseReaderLock();
                }
            }

            if(logicalExpression == null)
            {
                var lexer = new CalcLexer(new ANTLRStringStream(expression));
                var tokenStream = new CommonTokenStream(lexer);
                var parser = new CalcParser(tokenStream);

                logicalExpression = parser.calcExpression().value;

                if(parser.Errors != null && parser.Errors.Count > 0)
                {
                    throw new EvaluationException(String.Join(Environment.NewLine, parser.Errors.ToArray()));
                }

                if(_cacheEnabled && !nocache)
                {
                    try
                    {
                        Rwl.AcquireWriterLock(Timeout.Infinite);
                        _compiledExpressions[expression] = new WeakReference(logicalExpression);
                    }
                    finally
                    {
                        Rwl.ReleaseWriterLock();
                    }

                    CleanCache();

                    Trace.TraceInformation("Expression added to cache: " + expression);
                }
            }

            return logicalExpression;
        }

        /// <summary>
        /// Pre-compiles the expression in order to check syntax errors.
        /// If errors are detected, the Error property contains the message.
        /// </summary>
        /// <returns>True if the expression syntax is correct, otherwiser False</returns>
        public bool HasErrors()
        {
            try
            {
                if(ParsedExpression == null)
                {
                    ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache);
                }

                // In case HasErrors() is called multiple times for the same expression
                return ParsedExpression != null && Error != null;
            }
            catch(Exception e)
            {
                Error = e.Message;
                return true;
            }
        }

        public string Error { get; private set; }

        public LogicalExpression ParsedExpression { get; private set; }

        protected Dictionary<string, IEnumerator> ParameterEnumerators;
        protected Dictionary<string, object> ParametersBackup;

        public object Evaluate()
        {
            if(HasErrors())
            {
                throw new EvaluationException(Error);
            }

            if(ParsedExpression == null)
            {
                ParsedExpression = Compile(OriginalExpression, (Options & EvaluateOptions.NoCache) == EvaluateOptions.NoCache);
            }
            

            var visitor = new EvaluationVisitor(Options);
            visitor.EvaluateFunction += EvaluateFunction;
            visitor.EvaluateParameter += EvaluateParameter;
            visitor.Parameters = Parameters;
            visitor.Functions = Functions;

            // if array evaluation, execute the same expression multiple times
            if((Options & EvaluateOptions.IterateParameters) == EvaluateOptions.IterateParameters)
            {
                int size = -1;
                ParametersBackup = new Dictionary<string, object>();
                foreach(string key in Parameters.Keys)
                {
                    ParametersBackup.Add(key, Parameters[key]);
                }

                ParameterEnumerators = new Dictionary<string, IEnumerator>();

                foreach(object parameter in Parameters.Values)
                {
                    if(parameter is IEnumerable)
                    {
                        int localsize = 0;
                        foreach(object o in (IEnumerable)parameter)
                        {
                            localsize++;
                        }

                        if(size == -1)
                        {
                            size = localsize;
                        }
                        else if(localsize != size)
                        {
                            throw new EvaluationException("使用 IterateParameters 标识时, 与 IEnumerable 参数的集合大小必须匹配。");
                        }
                    }
                }

                foreach(string key in Parameters.Keys)
                {
                    var parameter = Parameters[key] as IEnumerable;
                    if(parameter != null)
                    {
                        ParameterEnumerators.Add(key, parameter.GetEnumerator());
                    }
                }

                var results = new List<object>();
                for(int i = 0 ; i < size ; i++)
                {
                    foreach(string key in ParameterEnumerators.Keys)
                    {
                        IEnumerator enumerator = ParameterEnumerators[key];
                        enumerator.MoveNext();
                        Parameters[key] = enumerator.Current;
                    }

                    ParsedExpression.Accept(visitor);
                    results.Add(visitor.Result);
                }

                return results;
            }

            ParsedExpression.Accept(visitor);
            return visitor.Result;

        }

        public event EvaluateFunctionHandler EvaluateFunction;
        public event EvaluateParameterHandler EvaluateParameter;

        private Dictionary<string, object> _parameters;

        public Dictionary<string, object> Parameters
        {
            get { return _parameters ?? (_parameters = new Dictionary<string, object>(((Options & EvaluateOptions.IgnoreCase) == EvaluateOptions.IgnoreCase) ? StringComparer.CurrentCultureIgnoreCase : null)); }
            set { _parameters = value; }
        }

        private Dictionary<string, UserFunction> _functions;
        public Dictionary<string, UserFunction> Functions
        {
            get { return _functions ?? (_functions = new Dictionary<string, UserFunction>(((Options & EvaluateOptions.IgnoreCase) == EvaluateOptions.IgnoreCase) ? StringComparer.CurrentCultureIgnoreCase : null)); }
            set { _functions = value; }
        }

        public void Set(string name, UserFunction function)
        {
            this.Functions[name] = function;
        }

        public void Set(string name, object value)
        {
            this.Parameters[name] = value;
        }
    }

    public delegate object UserFunction(UserFunctionArguments args);

    public class UserFunctionArguments
    {
        private FunctionArgs _args;

        public UserFunctionArguments(FunctionArgs args)
        {
            if(args == null) throw new ArgumentNullException("args");
            this._args = args;
        }

        public object this[int index]
        {
            get
            {
                if(index < 0 || index >= this._args.Parameters.Length) return null;
                return this._args.Parameters[index].Evaluate();
            }
        }

        public int ArgumentCount
        {
            get
            {
                return this._args.Parameters.Length;
            }
        }
    }
}
